Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unicode buffer overflow fix #14318

Closed
wants to merge 1 commit into from
Closed

unicode buffer overflow fix #14318

wants to merge 1 commit into from

Conversation

ryao
Copy link
Contributor

@ryao ryao commented Dec 23, 2022

Motivation and Context

As of Linux 6.2, -funsigned-char is set by the build system to change how C compilers interpret the char type so that type promotion to an int will always result in a positive number. This caused me to become concerned that miscompilation could happen, which lead to an earlier version of this PR that had a patch to disable that, plus a fix for a buffer overflow issue that I found when doing an audit of the code to find potential issues that change could cause.

The patch disabling -funsigned-char turned out to be an overreaction, so it has since been dropped from the PR. The patch fixing the buffer overflow is all that remains. Interestingly, the buffer overflow was not one of the bugs that I had expected to find during my audit while the bugs that I had expected to find do not appear to be present in the code. The buffer overflow is present both with and without -funsigned-char.

Description

I have fixed the buffer overflow that I found in module/unicode/u8_textprep.c.

How Has This Been Tested?

The buildbot can test it.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Performance enhancement (non-breaking change which improves efficiency)
  • Code cleanup (non-breaking change which makes code smaller or more readable)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Library ABI change (libzfs, libzfs_core, libnvpair, libuutil and libzfsbootenv)
  • Documentation (a change to man pages or other documentation)

Checklist:

@ryao ryao force-pushed the unsigned-char branch 3 times, most recently from 7cdc227 to ce11d11 Compare December 23, 2022 06:56
@ryao
Copy link
Contributor Author

ryao commented Dec 23, 2022

I changed the comment in module/Kbuild.in to be more succinct.

@ryao
Copy link
Contributor Author

ryao commented Dec 23, 2022

@ryao
Copy link
Contributor Author

ryao commented Dec 23, 2022

The buffer overflow bug was introduced into the repository by 42bcb36, which imported the OpenSolaris unicode library. It was introduced into OpenSolaris by illumos/illumos-gate@2765a47. It took over 15 years to be found.

@ryao ryao force-pushed the unsigned-char branch 3 times, most recently from a12918e to a12090d Compare December 23, 2022 21:06
@lukts30
Copy link

lukts30 commented Dec 23, 2022

OpenZFS is regularly compiled for the following architectures: aarch64, arm, ppc, ppc64, x86, x86_64.
https://openzfs.github.io/openzfs-docs/Project%20and%20Community/FAQ.html#supported-architectures

Out of the platforms mentioned, only x86 and x86_64 use signed char. Any code in ZFS that breaks on x86 and x86_64 when using -funsigned-char is already broken on aarch64, arm, ppc, and ppc64.

Linux is changing to-funsigned-char primarily because they just want one consistent definition of char across all architectures.
The initial proposed change was even to use the "x86-centric" -fsigned-char. This change is not primarily motivated by extended ASCII handling.
https://lkml.org/lkml/2022/10/19/2071
https://lwn.net/Articles/911914/

"char" has something else unusual going on. If you compare it to a "unsigned char" in an if-condition, and compile with -Wall -Wextra, you'll get a warning about casting. If you compare it to a "signed char" on the same system, you'll still get the warning! In fact "char" is not considered to be exactly the same as "signed char" or "unsigned char", it has 3 variants!
https://news.ycombinator.com/item?id=18269886

@ryao
Copy link
Contributor Author

ryao commented Dec 24, 2022

OpenZFS is regularly compiled for the following architectures: aarch64, arm, ppc, ppc64, x86, x86_64.
https://openzfs.github.io/openzfs-docs/Project%20and%20Community/FAQ.html#supported-architectures

Out of the platforms mentioned, only x86 and x86_64 use signed char. Any code in ZFS that breaks on x86 and x86_64 when using -funsigned-char is already broken on aarch64, arm, ppc, and ppc64.

Linux is changing to-funsigned-char primarily because they just want one consistent definition of char across all architectures. The initial proposed change was even to use the "x86-centric" -fsigned-char. This change is not primarily motivated by extended ASCII handling. https://lkml.org/lkml/2022/10/19/2071 https://lwn.net/Articles/911914/

"char" has something else unusual going on. If you compare it to a "unsigned char" in an if-condition, and compile with -Wall -Wextra, you'll get a warning about casting. If you compare it to a "signed char" on the same system, you'll still get the warning! In fact "char" is not considered to be exactly the same as "signed char" or "unsigned char", it has 3 variants!
https://news.ycombinator.com/item?id=18269886

I had not known a number of those things, but the code in this PR is fine as it is because the problematic char usage in the unicode module uses int8_t. I had just treated it as char, as did clang-tidy. That said, we still should want our userland and kernel versions to be built the same way on the same platform.

module/Kbuild.in Outdated Show resolved Hide resolved
@tonyhutter
Copy link
Contributor

The more I think about this, the more I think we should actually force -funsigned-char to be enabled in master and see what happens. Especially considering if there were any bad char -> int conversions, we would have hopefully seen them already in our ARM buildbot builders (where char == unsigned char).

@ryao
Copy link
Contributor Author

ryao commented Dec 27, 2022

The more I think about this, the more I think we should actually force -funsigned-char to be enabled in master and see what happens. Especially considering if there were any bad char -> int conversions, we would have hopefully seen them already in our ARM buildbot builders (where char == unsigned char).

I came to the same conclusion while thinking about this over Christmas (thanks to @lukts30’s feedback), but I was too busy to update this today to reflect that. I will update it tomorrow to reflect that.

@ckane
Copy link
Contributor

ckane commented Dec 27, 2022

FYI I've got these changes, plus what I submitted in #14331 running on my desktop on 6.2rc1 right now.

@ryao
Copy link
Contributor Author

ryao commented Dec 27, 2022

FYI I've got these changes, plus what I submitted in #14331 running on my desktop on 6.2rc1 right now.

The patch here for 6.2 compatibility was an overreaction on my part. It is not necessary for 6.2 compatibility and the code should compile the same way regardless of whether the 6.2 patch from here is applied. It will be removed from this PR shortly.

In do_composition(), we have:

size = u8_number_of_bytes[*p];
if (size <= 1 || (p + size) > oslast)
    break;

There, we have type promotion from char to size_t, which is unsigned. C
will sign extend the value as part of the widening before treating the
value as unsigned and the negative values we can counter are error
values from U8_ILLEGAL_CHAR and U8_OUT_OF_RANGE_CHAR, which are -1 and
-2 respectively. The unsigned versions of these under two's complement
are SIZE_MAX and SIZE_MAX-1 respectively.

The bounds check is written under the assumption that `size <= 1` does a
signed comparison. This is followed by a pointer comparison to see if
the string has the correct length, which is fine.

A little further down we have:

for (i = 0; i < size; i++)
    tc[i] = *p++;

When an error condition is encountered, this will attempt to iterate at
least SIZE_MAX-1 times, which will massively overflow the buffer, which
is not fine.

The kernel will kill the loop as soon as it hits the kernel stack guard
on Linux systems built with CONFIG_VMAP_STACK=y, which should be just
about all of them. That prevents arbitrary code execution and just about
any other bad thing that a black hat attacker might attempt with
knowledge of this buffer overflow. Other systems' kernels have
mitigations for unbounded in-kernel buffer overflows that will catch
this too.

Signed-off-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
@ryao ryao changed the title Linux 6.2 compatibility + unicode buffer overflow fix unicode buffer overflow fix Dec 27, 2022
@ryao
Copy link
Contributor Author

ryao commented Dec 27, 2022

This has been updated to drop the patch meant to disable -funsigned-char on Linux 6.2.

@ryao ryao requested review from tonyhutter and ckane and removed request for ckane December 27, 2022 10:27
Copy link
Contributor

@ckane ckane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is testing fine on my amd64 system w/ 6.2rc1

@tonyhutter
Copy link
Contributor

There, we have type promotion from char to size_t ...

Just for accuracy - can you replace "char" with "int8_t" in your commit message?:

const int8_t u8_number_of_bytes[0x100] = {

@ryao
Copy link
Contributor Author

ryao commented Dec 29, 2022

There, we have type promotion from char to size_t ...

Just for accuracy - can you replace "char" with "int8_t" in your commit message?:

const int8_t u8_number_of_bytes[0x100] = {

Nice catch. That is from the 23rd when I still thought clang-tidy only reported things of type char, so I had mistakenly thought it was a char. After correcting a number of mistakes in my understanding of things, I ended up reaching the same conclusion, but forgot to correct that text in an oversight.

That said, the illumos developers semi-independently wrote their own fix for this based on an email I sent them with a description of what I found. I had sent it later on the 23rd following a suggestion in slack that I should contact them about this. Anyway, they pushed illumos/illumos-gate@f137b22 on the 24th to the illumos-gate repository to fix this. Their fix is just as good at addressing this, but is easier to read since it does not require reviewers think about operator precedence or how type casting works like my fix did.

After thinking about it, I will withdraw this PR in favor of one porting their patch, since there is no point in us being different in this code. They did not write much about the patch (and likely in their haste, failed to credit me as the reporter/original-patch-author), so I will include a corrected version of the commit message from this patch in that port to properly document it when I push it. I will close this after I do that.

ryao pushed a commit to ryao/zfs that referenced this pull request Dec 29, 2022
Authored by: Dan McDonald <danmcd@mnx.io>
Reviewed by: Patrick Mooney <pmooney@pfmooney.com>
Reviewed by: Richard Lowe <richlowe@richlowe.net>
Approved by: Joshua M. Clulow <josh@sysmgr.org>
Ported-by: Richard Yao <richard.yao@alumni.stonybrook.edu>

Illumos-issue: https://www.illumos.org/issues/15286
Illumos-commit: illumos/illumos-gate@f137b22

Porting Notes:

The patch in illumos did not have much of a commit message, and did not
provide attribution to the reporter, while original patch proposed to
OpenZFS did, so I am listing the reporter (myself) and original patch
author (also myself) below while including the original commit message
with some minor corrections as part of the porting notes:

In do_composition(), we have:

size = u8_number_of_bytes[*p];
if (size <= 1 || (p + size) > oslast)
	break;

There, we have type promotion from int8_t to size_t, which is unsigned.
C will sign extend the value as part of the widening before treating the
value as unsigned and the negative values we can counter are error
values from U8_ILLEGAL_CHAR and U8_OUT_OF_RANGE_CHAR, which are -1 and
-2 respectively. The unsigned versions of these under two's complement
are SIZE_MAX and SIZE_MAX-1 respectively.

The bounds check is written under the assumption that `size <= 1` does a
signed comparison. This is followed by a pointer comparison to see if
the string has the correct length, which is fine.

A little further down we have:

for (i = 0; i < size; i++)
	tc[i] = *p++;

When an error condition is encountered, this will attempt to iterate at
least SIZE_MAX-1 times, which will massively overflow the buffer, which
is not fine.

The kernel will kill the loop as soon as it hits the kernel stack guard
on Linux systems built with CONFIG_VMAP_STACK=y, which should be just
about all of them. That prevents arbitrary code execution and just about
any other bad thing that a black hat attacker might attempt with
knowledge of this buffer overflow. Other systems' kernels have
mitigations for unbounded in-kernel buffer overflows that will catch
this too.

Also, the patch in illumos-gate made an effort to fix C style issues
that had been fixed in the OpenZFS/ZFSOnLinux repository. Those issues
had been mentioned in the email that I originally sent them about this
issue. One of the fixes had not been already done, so it is included.
Another to collect_a_seq()'s arguments was handled differently in
OpenZFS. For the sake of avoiding unnecessary differences, it has been
adopted. This has the interesting effect that if you correct the paths
in the illumos-gate patch to match the current OpenZFS repository, you
can reverse apply it cleanly.

Original-patch-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Reported-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Closes openzfs#14318
@ryao
Copy link
Contributor Author

ryao commented Dec 29, 2022

A port of the illumos patch fixing this has been put into #14342. I addressed @tonyhutter's remark when doing the port. I am closing this as it is now obsolete.

@ryao ryao closed this Dec 29, 2022
behlendorf pushed a commit that referenced this pull request Jan 5, 2023
Authored by: Dan McDonald <danmcd@mnx.io>
Reviewed by: Patrick Mooney <pmooney@pfmooney.com>
Reviewed by: Richard Lowe <richlowe@richlowe.net>
Approved by: Joshua M. Clulow <josh@sysmgr.org>
Ported-by: Richard Yao <richard.yao@alumni.stonybrook.edu>

Illumos-issue: https://www.illumos.org/issues/15286
Illumos-commit: illumos/illumos-gate@f137b22

Porting Notes:

The patch in illumos did not have much of a commit message, and did not
provide attribution to the reporter, while original patch proposed to
OpenZFS did, so I am listing the reporter (myself) and original patch
author (also myself) below while including the original commit message
with some minor corrections as part of the porting notes:

In do_composition(), we have:

size = u8_number_of_bytes[*p];
if (size <= 1 || (p + size) > oslast)
	break;

There, we have type promotion from int8_t to size_t, which is unsigned.
C will sign extend the value as part of the widening before treating the
value as unsigned and the negative values we can counter are error
values from U8_ILLEGAL_CHAR and U8_OUT_OF_RANGE_CHAR, which are -1 and
-2 respectively. The unsigned versions of these under two's complement
are SIZE_MAX and SIZE_MAX-1 respectively.

The bounds check is written under the assumption that `size <= 1` does a
signed comparison. This is followed by a pointer comparison to see if
the string has the correct length, which is fine.

A little further down we have:

for (i = 0; i < size; i++)
	tc[i] = *p++;

When an error condition is encountered, this will attempt to iterate at
least SIZE_MAX-1 times, which will massively overflow the buffer, which
is not fine.

The kernel will kill the loop as soon as it hits the kernel stack guard
on Linux systems built with CONFIG_VMAP_STACK=y, which should be just
about all of them. That prevents arbitrary code execution and just about
any other bad thing that a black hat attacker might attempt with
knowledge of this buffer overflow. Other systems' kernels have
mitigations for unbounded in-kernel buffer overflows that will catch
this too.

Also, the patch in illumos-gate made an effort to fix C style issues
that had been fixed in the OpenZFS/ZFSOnLinux repository. Those issues
had been mentioned in the email that I originally sent them about this
issue. One of the fixes had not been already done, so it is included.
Another to collect_a_seq()'s arguments was handled differently in
OpenZFS. For the sake of avoiding unnecessary differences, it has been
adopted. This has the interesting effect that if you correct the paths
in the illumos-gate patch to match the current OpenZFS repository, you
can reverse apply it cleanly.

Original-patch-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Reported-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Co-authored-by: Dan McDonald <danmcd@mnx.io>
Closes #14318
Closes #14342
tonyhutter pushed a commit to tonyhutter/zfs that referenced this pull request Jan 13, 2023
Authored by: Dan McDonald <danmcd@mnx.io>
Reviewed by: Patrick Mooney <pmooney@pfmooney.com>
Reviewed by: Richard Lowe <richlowe@richlowe.net>
Approved by: Joshua M. Clulow <josh@sysmgr.org>
Ported-by: Richard Yao <richard.yao@alumni.stonybrook.edu>

Illumos-issue: https://www.illumos.org/issues/15286
Illumos-commit: illumos/illumos-gate@f137b22

Porting Notes:

The patch in illumos did not have much of a commit message, and did not
provide attribution to the reporter, while original patch proposed to
OpenZFS did, so I am listing the reporter (myself) and original patch
author (also myself) below while including the original commit message
with some minor corrections as part of the porting notes:

In do_composition(), we have:

size = u8_number_of_bytes[*p];
if (size <= 1 || (p + size) > oslast)
	break;

There, we have type promotion from int8_t to size_t, which is unsigned.
C will sign extend the value as part of the widening before treating the
value as unsigned and the negative values we can counter are error
values from U8_ILLEGAL_CHAR and U8_OUT_OF_RANGE_CHAR, which are -1 and
-2 respectively. The unsigned versions of these under two's complement
are SIZE_MAX and SIZE_MAX-1 respectively.

The bounds check is written under the assumption that `size <= 1` does a
signed comparison. This is followed by a pointer comparison to see if
the string has the correct length, which is fine.

A little further down we have:

for (i = 0; i < size; i++)
	tc[i] = *p++;

When an error condition is encountered, this will attempt to iterate at
least SIZE_MAX-1 times, which will massively overflow the buffer, which
is not fine.

The kernel will kill the loop as soon as it hits the kernel stack guard
on Linux systems built with CONFIG_VMAP_STACK=y, which should be just
about all of them. That prevents arbitrary code execution and just about
any other bad thing that a black hat attacker might attempt with
knowledge of this buffer overflow. Other systems' kernels have
mitigations for unbounded in-kernel buffer overflows that will catch
this too.

Also, the patch in illumos-gate made an effort to fix C style issues
that had been fixed in the OpenZFS/ZFSOnLinux repository. Those issues
had been mentioned in the email that I originally sent them about this
issue. One of the fixes had not been already done, so it is included.
Another to collect_a_seq()'s arguments was handled differently in
OpenZFS. For the sake of avoiding unnecessary differences, it has been
adopted. This has the interesting effect that if you correct the paths
in the illumos-gate patch to match the current OpenZFS repository, you
can reverse apply it cleanly.

Original-patch-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Reported-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Co-authored-by: Dan McDonald <danmcd@mnx.io>
Closes openzfs#14318
Closes openzfs#14342
tonyhutter pushed a commit to tonyhutter/zfs that referenced this pull request Jan 18, 2023
Authored by: Dan McDonald <danmcd@mnx.io>
Reviewed by: Patrick Mooney <pmooney@pfmooney.com>
Reviewed by: Richard Lowe <richlowe@richlowe.net>
Approved by: Joshua M. Clulow <josh@sysmgr.org>
Ported-by: Richard Yao <richard.yao@alumni.stonybrook.edu>

Illumos-issue: https://www.illumos.org/issues/15286
Illumos-commit: illumos/illumos-gate@f137b22

Porting Notes:

The patch in illumos did not have much of a commit message, and did not
provide attribution to the reporter, while original patch proposed to
OpenZFS did, so I am listing the reporter (myself) and original patch
author (also myself) below while including the original commit message
with some minor corrections as part of the porting notes:

In do_composition(), we have:

size = u8_number_of_bytes[*p];
if (size <= 1 || (p + size) > oslast)
	break;

There, we have type promotion from int8_t to size_t, which is unsigned.
C will sign extend the value as part of the widening before treating the
value as unsigned and the negative values we can counter are error
values from U8_ILLEGAL_CHAR and U8_OUT_OF_RANGE_CHAR, which are -1 and
-2 respectively. The unsigned versions of these under two's complement
are SIZE_MAX and SIZE_MAX-1 respectively.

The bounds check is written under the assumption that `size <= 1` does a
signed comparison. This is followed by a pointer comparison to see if
the string has the correct length, which is fine.

A little further down we have:

for (i = 0; i < size; i++)
	tc[i] = *p++;

When an error condition is encountered, this will attempt to iterate at
least SIZE_MAX-1 times, which will massively overflow the buffer, which
is not fine.

The kernel will kill the loop as soon as it hits the kernel stack guard
on Linux systems built with CONFIG_VMAP_STACK=y, which should be just
about all of them. That prevents arbitrary code execution and just about
any other bad thing that a black hat attacker might attempt with
knowledge of this buffer overflow. Other systems' kernels have
mitigations for unbounded in-kernel buffer overflows that will catch
this too.

Also, the patch in illumos-gate made an effort to fix C style issues
that had been fixed in the OpenZFS/ZFSOnLinux repository. Those issues
had been mentioned in the email that I originally sent them about this
issue. One of the fixes had not been already done, so it is included.
Another to collect_a_seq()'s arguments was handled differently in
OpenZFS. For the sake of avoiding unnecessary differences, it has been
adopted. This has the interesting effect that if you correct the paths
in the illumos-gate patch to match the current OpenZFS repository, you
can reverse apply it cleanly.

Original-patch-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Reported-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Co-authored-by: Dan McDonald <danmcd@mnx.io>
Closes openzfs#14318
Closes openzfs#14342
tonyhutter pushed a commit to tonyhutter/zfs that referenced this pull request Jan 19, 2023
Authored by: Dan McDonald <danmcd@mnx.io>
Reviewed by: Patrick Mooney <pmooney@pfmooney.com>
Reviewed by: Richard Lowe <richlowe@richlowe.net>
Approved by: Joshua M. Clulow <josh@sysmgr.org>
Ported-by: Richard Yao <richard.yao@alumni.stonybrook.edu>

Illumos-issue: https://www.illumos.org/issues/15286
Illumos-commit: illumos/illumos-gate@f137b22

Porting Notes:

The patch in illumos did not have much of a commit message, and did not
provide attribution to the reporter, while original patch proposed to
OpenZFS did, so I am listing the reporter (myself) and original patch
author (also myself) below while including the original commit message
with some minor corrections as part of the porting notes:

In do_composition(), we have:

size = u8_number_of_bytes[*p];
if (size <= 1 || (p + size) > oslast)
	break;

There, we have type promotion from int8_t to size_t, which is unsigned.
C will sign extend the value as part of the widening before treating the
value as unsigned and the negative values we can counter are error
values from U8_ILLEGAL_CHAR and U8_OUT_OF_RANGE_CHAR, which are -1 and
-2 respectively. The unsigned versions of these under two's complement
are SIZE_MAX and SIZE_MAX-1 respectively.

The bounds check is written under the assumption that `size <= 1` does a
signed comparison. This is followed by a pointer comparison to see if
the string has the correct length, which is fine.

A little further down we have:

for (i = 0; i < size; i++)
	tc[i] = *p++;

When an error condition is encountered, this will attempt to iterate at
least SIZE_MAX-1 times, which will massively overflow the buffer, which
is not fine.

The kernel will kill the loop as soon as it hits the kernel stack guard
on Linux systems built with CONFIG_VMAP_STACK=y, which should be just
about all of them. That prevents arbitrary code execution and just about
any other bad thing that a black hat attacker might attempt with
knowledge of this buffer overflow. Other systems' kernels have
mitigations for unbounded in-kernel buffer overflows that will catch
this too.

Also, the patch in illumos-gate made an effort to fix C style issues
that had been fixed in the OpenZFS/ZFSOnLinux repository. Those issues
had been mentioned in the email that I originally sent them about this
issue. One of the fixes had not been already done, so it is included.
Another to collect_a_seq()'s arguments was handled differently in
OpenZFS. For the sake of avoiding unnecessary differences, it has been
adopted. This has the interesting effect that if you correct the paths
in the illumos-gate patch to match the current OpenZFS repository, you
can reverse apply it cleanly.

Original-patch-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Reported-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Co-authored-by: Dan McDonald <danmcd@mnx.io>
Closes openzfs#14318
Closes openzfs#14342
lundman pushed a commit to openzfsonwindows/openzfs that referenced this pull request Mar 3, 2023
Authored by: Dan McDonald <danmcd@mnx.io>
Reviewed by: Patrick Mooney <pmooney@pfmooney.com>
Reviewed by: Richard Lowe <richlowe@richlowe.net>
Approved by: Joshua M. Clulow <josh@sysmgr.org>
Ported-by: Richard Yao <richard.yao@alumni.stonybrook.edu>

Illumos-issue: https://www.illumos.org/issues/15286
Illumos-commit: illumos/illumos-gate@f137b22

Porting Notes:

The patch in illumos did not have much of a commit message, and did not
provide attribution to the reporter, while original patch proposed to
OpenZFS did, so I am listing the reporter (myself) and original patch
author (also myself) below while including the original commit message
with some minor corrections as part of the porting notes:

In do_composition(), we have:

size = u8_number_of_bytes[*p];
if (size <= 1 || (p + size) > oslast)
	break;

There, we have type promotion from int8_t to size_t, which is unsigned.
C will sign extend the value as part of the widening before treating the
value as unsigned and the negative values we can counter are error
values from U8_ILLEGAL_CHAR and U8_OUT_OF_RANGE_CHAR, which are -1 and
-2 respectively. The unsigned versions of these under two's complement
are SIZE_MAX and SIZE_MAX-1 respectively.

The bounds check is written under the assumption that `size <= 1` does a
signed comparison. This is followed by a pointer comparison to see if
the string has the correct length, which is fine.

A little further down we have:

for (i = 0; i < size; i++)
	tc[i] = *p++;

When an error condition is encountered, this will attempt to iterate at
least SIZE_MAX-1 times, which will massively overflow the buffer, which
is not fine.

The kernel will kill the loop as soon as it hits the kernel stack guard
on Linux systems built with CONFIG_VMAP_STACK=y, which should be just
about all of them. That prevents arbitrary code execution and just about
any other bad thing that a black hat attacker might attempt with
knowledge of this buffer overflow. Other systems' kernels have
mitigations for unbounded in-kernel buffer overflows that will catch
this too.

Also, the patch in illumos-gate made an effort to fix C style issues
that had been fixed in the OpenZFS/ZFSOnLinux repository. Those issues
had been mentioned in the email that I originally sent them about this
issue. One of the fixes had not been already done, so it is included.
Another to collect_a_seq()'s arguments was handled differently in
OpenZFS. For the sake of avoiding unnecessary differences, it has been
adopted. This has the interesting effect that if you correct the paths
in the illumos-gate patch to match the current OpenZFS repository, you
can reverse apply it cleanly.

Original-patch-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Reported-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Co-authored-by: Dan McDonald <danmcd@mnx.io>
Closes openzfs#14318
Closes openzfs#14342
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants